package terrain;

import main.Unit;
import main.Utils;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

public class HighWallRect extends Terrain{
	
	public static final int FULLY_BLOCKED = 2;
	public static final int PARTIAL_BLOCKED = 1;
	public static final int NOT_BLOCKED = 0;
	
	
	//
	private Area lastSight;
	private Area lastRotate;
	//
	
	public HighWallRect(double x1, double y1, double width, double height, double rotationCW) {
		Shape s = new Rectangle2D.Double(x1, y1, width, height);
		obstacle = new Area(s);
		AffineTransform rotate = AffineTransform.getRotateInstance(rotationCW, x1, y1);
		obstacle.transform(rotate);
	}

	public Area getObstacle() {
		// TODO Auto-generated method stub
		return this.obstacle;
	}
	
	/*
	 * Return values:
	 * 		0 / NOT_BLOCKED - sight of target not blocked by obstacle
	 * 		1 / PARTIAL_BLOCKED - sight of target partially blocked by obstacle
	 * 		2 / FULLY_BLOCKED - sight fully blocked
	 */
	public int blocksLOS(Unit looker, Unit target){
		double theta = Math.atan2(target.y-looker.y, target.x-looker.x);
		double dist = Math.sqrt(Math.pow(target.x-looker.x, 2) + Math.pow(target.y-looker.y, 2));

		// Make an Area representing sight line, clone it for two tests
		Polygon sightTriangle = new Polygon();
		sightTriangle.addPoint(looker.x, looker.y);
		sightTriangle.addPoint((int) (looker.x+dist), looker.y-target.pixelRadius);
		sightTriangle.addPoint((int) (looker.x+dist), looker.y+target.pixelRadius);
		Area sightArea = new Area(sightTriangle);
		
		lastSight = (Area)sightArea.clone();
		AffineTransform rotate = AffineTransform.getRotateInstance(theta, looker.x, looker.y);
		sightArea.transform(rotate);
		Area intersectArea = (Area) sightArea.clone();
		lastRotate = (Area)sightArea.clone();
		
		// If subtracting obstacle makes isSingular false, then fully blocked
		sightArea.subtract(obstacle);
		//System.out.println(Utils.areaIsConnected(sightArea));
		if(!Utils.areaIsConnected(sightArea)) {
			return FULLY_BLOCKED;
		}
		
		// Else if intersecting is non-empty, then partially blocked
		intersectArea.intersect(obstacle);
		if(!intersectArea.isEmpty()) {
			return PARTIAL_BLOCKED;
		}
		
		// For units: If partially blocked but in contact, can nullify that penalty 
		// If fully blocked, still a problem.
		return NOT_BLOCKED;
	}
	
	
	public Point2D contactPoint(int rootX, int rootY, int destX, int destY, int pixelRadius) {
		//System.out.println("Checking contact with line ("+rootX+", "+rootY+") and ("+destX+", " + destY+ ")");
		double slope = 1000000000;
		if(destX != rootX)
			slope = (destY-rootY)*1.0/(destX-rootX);
		double intercept = rootY - slope * rootX;
		
		PathIterator pi  = obstacle.getPathIterator(null);
		double[] coords = {0,0,0,0,0,0};
		
		int moveType = pi.currentSegment(coords);
		double prevX = coords[0];
		double prevY = coords[1];
		double currX = 0;
		double currY = 0;
		double candidateX = 0;
		double candidateY = 0;
		Point2D rootPoint = new Point2D.Double(rootX, rootY);
		Point2D closePoint = new Point2D.Double(-1, -1);
		pi.next();
		
		while (moveType != PathIterator.SEG_CLOSE){
			moveType = pi.currentSegment(coords);
			currX = coords[0];
			currY = coords[1];
			if (currY == prevY && currX == prevX){
				pi.next();
				continue;
			}
			//System.out.println("Checking line between ("+prevX+", "+prevY+") and ("+currX+", " + currY+ ")");
			
			// If we intersect, or any of the endpoints are within a base radius, we're going to contact this obstacle
			if (Utils.segmentIntersect(rootX, rootY, destX, destY, prevX, prevY, currX, currY) ||
					Utils.distanceFromLine(destX, destY, prevX, prevY, currX, currY, false) <= pixelRadius-1 ||
					Utils.distanceFromLine(prevX, prevY, rootX, rootY, destX, destY, false) <= pixelRadius-1 ||
					Utils.distanceFromLine(currX, currY, rootX, rootY, destX, destY, false) <= pixelRadius-1) {
				//System.out.println("It's close enough!H");
				Line2D wallSeg = new Line2D.Double(prevX, prevY, currX, currY);
				// Calculate the point we hit at with a simple line intersection with a parallel line
				// to the obstacle one
				if (currX == prevX) {   // Target line is vertical
					if (rootX < currX)
						candidateX = currX-pixelRadius;   // Approaching from left
					else
						candidateX = currX+pixelRadius;   // Approaching from right
					if (closePoint.getX() == -1 && 
							wallSeg.ptSegDist(candidateX, slope*candidateX + intercept) <= pixelRadius+2){
						closePoint = new Point2D.Double(candidateX, slope*candidateX + intercept);
					}
					else {
						candidateY = slope*candidateX + intercept;
						if (rootPoint.distance(candidateX, candidateY) < rootPoint.distance(closePoint) && 
							wallSeg.ptSegDist(candidateX, slope*candidateX + intercept) <= pixelRadius+2) {
							closePoint = new Point2D.Double(candidateX, candidateY);
						}
					}
				}
				else if (currY == prevY) {   // Target line is horizontal
					if (rootY < currY)
						candidateY = currY-pixelRadius;   // Approaching from below
					else
						candidateY = currY+pixelRadius;   // Approaching from right
					if (closePoint.getX() == -1 && 
							wallSeg.ptSegDist((candidateY-intercept)/slope, candidateY) <= pixelRadius+2){
						if (slope == 0)
							slope += 0.000001;
						closePoint = new Point2D.Double((candidateY-intercept)/slope, candidateY);
					}
					else {
						if (slope == 0)
							slope += 0.000001;
						candidateX = (candidateY-intercept)/slope;
						if (rootPoint.distance(candidateX, candidateY) < rootPoint.distance(closePoint) && 
								wallSeg.ptSegDist((candidateY-intercept)/slope, candidateY) <= pixelRadius+2) {
							closePoint = new Point2D.Double(candidateX, candidateY);
						}
					}
				}
				else {      // Normal wall line
					double wallSlope = (currY-prevY)/(currX-prevX);
					double wallInt = currY - wallSlope*currX;
					//System.out.println("Base wall slope and intersect:" + wallSlope + " " + wallInt);
					double yChange = Math.abs(pixelRadius/Math.cos(Math.atan2(currY-prevY, currX-prevX)));
					//System.out.println("atan2: " + Math.atan2(currY-prevY, currX-prevX));
					//System.out.println("ychange: " + yChange);
					double newInt;
					if (rootY <= wallSlope*rootX + wallInt) { // Below the line
						newInt = wallInt - yChange;
					}
					else{
						newInt = wallInt + yChange;
					}
					//System.out.println("Extended y-intersect: " + newInt);
					Point2D intersect = Utils.findLineIntersect(rootX, rootY, destX, destY, currX, currX*wallSlope+newInt, prevX, prevX*wallSlope+newInt);
					//System.out.println("Intersect point: (" + intersect.getX() + ", " + intersect.getY()+")");
					if(wallSeg.ptSegDist(intersect) > pixelRadius+2) {
						prevX = currX;
						prevY = currY;
						pi.next();
						continue; // There will be a better one.
					}
					
					if (closePoint.getX() == -1) {
						//System.out.println("Also, it's closer!");
						closePoint = (Point2D)intersect.clone();
					}
					else if (rootPoint.distance(intersect) < rootPoint.distance(closePoint)) {
						//System.out.println("Also, it's closer!");
						closePoint = (Point2D)intersect.clone();
					}
				}
			}
			prevX = currX;
			prevY = currY;
			pi.next();
		}
		
		//System.out.println("Closest contact point: " + closePoint.getX() + " " + closePoint.getY());
		if (closePoint.getX() == -1)
			return null;
		return closePoint;
	}
	

	
	public void drawTo(Graphics g){
		AffineTransform offsetShift = AffineTransform.getTranslateInstance(-1*board.game.myUI.boardOffsetX, -1*board.game.myUI.boardOffsetY);
		Area drawClone = (Area) obstacle.clone();
		drawClone.transform(offsetShift);
		((Graphics2D)g).fill(drawClone);
	}
	
	// Debug methods
	public void drawLastCheck(Graphics g){
		if(lastSight != null && lastRotate != null){
			((Graphics2D)g).fill(lastSight);
			((Graphics2D)g).fill(lastRotate);
		}
	}
	
	public String toMessageString() {
		return "HighWall";
	}
	
}